package com.macro.mall.common.aspect;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.druid.util.StringUtils;
import com.macro.mall.common.annotation.AccessLimit;
import com.macro.mall.common.exception.AccessLimitException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;

/**
 * 新增接口防刷机制，默认5秒内访问次数不得超过5次
 *
 * @author：郎
 * @name：AccessLimitInterceptor
 * @date：2024/3/16 14:43
 */
@Component
@Aspect
public class AccessLimitInterceptor {

    private RedisTemplate<String, Object> redisTemplate;
    private HttpServletRequest request;

    public AccessLimitInterceptor(RedisTemplate<String, Object> redisTemplate, HttpServletRequest request) {
        this.request = request;
        this.redisTemplate = redisTemplate;
    }

    @Around("@annotation(accessLimit)")
    public Object around(ProceedingJoinPoint joinPoint, AccessLimit accessLimit) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String ip = request.getHeader("X-Real-IP");
        if (StringUtils.isEmpty(ip)) {
            ip = request.getRemoteAddr();
        }
        String uri = request.getRequestURI();
        if ("/admin/login".equals(uri)) {
            String key = "login:" + uri + ":" + ip;
            ValueOperations<String, Object> loginOperation = redisTemplate.opsForValue();
            Integer times = (Integer) loginOperation.get(key);
            if (ObjectUtil.isNull(times)) {
                loginOperation.set(key, 1, accessLimit.seconds(), TimeUnit.SECONDS);
            } else if (times < accessLimit.maxTimes()) {
                loginOperation.increment(key, 1);
            } else {
                loginOperation.increment(key, 1);
                //计算出本次需要推迟多久
                long delay = calculateDelay(accessLimit, (int) loginOperation.get(key));
                //计算当前剩余过期时间
                Long currExpire = redisTemplate.getExpire(key, TimeUnit.SECONDS);
                redisTemplate.expire(key, currExpire + delay, TimeUnit.SECONDS);
                throw new AccessLimitException("您登录的过于频繁，请[" + (currExpire + delay) + "]秒后再试！");
            }
        } else {
            String url = request.getRequestURL().toString();
            String key = "access:" + url + ":" + ip;
            ValueOperations<String, Object> operations = redisTemplate.opsForValue();
            Integer count = (Integer) operations.get(key);
            if (count == null) {
                operations.set(key, 1, accessLimit.seconds(), TimeUnit.SECONDS);
            } else if (count < accessLimit.maxTimes()) {
                operations.increment(key, 1);
            } else {
                throw new AccessLimitException("接口访问过于频繁，请稍后再试！");
            }
        }

        return joinPoint.proceed();
    }

    private static long calculateDelay(AccessLimit accessLimit, int times) {
        //Math.pow(第一个参数是底数（base），第二个参数是指数（exponent），返回结果为底数的指数次幂)
        return (long) (times * Math.pow(accessLimit.defaultBaseSeconds(), times - 1));
    }
}
